/*
 * 
 * Copyright 2010 David H Pollack
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 * 
 * 
 */

package com.dhpollack.rotogss.servlets;

import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.url;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.datanucleus.store.appengine.query.JDOCursorHelper;

import com.dhpollack.rotogss.data.AppConfig;
import com.dhpollack.rotogss.data.RotoGSSTeams;
import com.dhpollack.rotogss.data.RotoGSSUsers;
import com.dhpollack.rotogss.data.YahooGames;
import com.dhpollack.rotogss.data.YahooGamesStats;
import com.dhpollack.rotogss.data.YahooLeagueTeamStandings;
import com.dhpollack.rotogss.data.YahooLeagueTeams;
import com.dhpollack.rotogss.data.YahooLeagues;
import com.dhpollack.rotogss.helper.AppHelper;
import com.dhpollack.rotogss.helper.PMF;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.gdata.client.authn.oauth.GoogleOAuthParameters;
import com.google.gdata.client.authn.oauth.OAuthException;
import com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
import com.google.gdata.client.docs.DocsService;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.Link;
import com.google.gdata.data.batch.*;
import com.google.gdata.data.spreadsheet.Field;
import com.google.gdata.data.spreadsheet.RecordEntry;
import com.google.gdata.data.spreadsheet.RecordFeed;
import com.google.gdata.util.ServiceException;

/**
 * @author davidpl
 *
 */
public class GStoreStandingsServlet extends HttpServlet {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 5914970229401739384L;

	private DocsService docsClient = new DocsService("dhpollack-RotoGSS-v1");
	
	private SpreadsheetService spreadClient = new SpreadsheetService("dhpollack-RotoGSS-v1");
	
	private static final AppConfig appConfig = AppHelper.get();
	
	private static final String BASE_DOCS_URL = "https://docs.google.com/feeds/default/private/full/";
	
	private static final String BASE_SS_URL = "https://spreadsheets.google.com/feeds/";
	
	private static int NUM_TEAMS;
	
	private PersistenceManager pm = PMF.get().getPersistenceManager();
	
	private DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	
	private Queue queue = QueueFactory.getDefaultQueue();
	
	private MessageDigest m;
	
	private static final boolean USE_BATCH = false;
	
	public void doGet(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
		doPost(req, resp);
	}
	
	public void doPost(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
		
		setMessageDigester();
		
		
		String user = req.getParameter("user");
		
		if(user == null) {
			log("No user set");
			return;
		}
		
		String team = req.getParameter("team");
		
		if(team == null) {
			log("No team set");
			return;
		}
		
		String lastCursorString = req.getParameter("cursor");
		
		String nth = req.getParameter("nth"); 
		
		Integer n;
		
		if(nth == null) {
			n = 1;
		} else {
			n = Integer.valueOf(nth);
		}

		boolean callSelf = false;
		
		/*
		 * Get User Information and set Google OAuth parameters
		 * for subsequent calls to the Documents service.
		 * 
		 */
		RotoGSSUsers gss_user;
		try {
			Key user_key = new KeyFactory.Builder(
					RotoGSSUsers.class.getSimpleName(), user).getKey();
			gss_user = pm.getObjectById(RotoGSSUsers.class, user_key);
			setOAuthCredentials(gss_user);
		} catch (JDOObjectNotFoundException e) {
			log("User Not Found: " + user);
			e.printStackTrace();
			return;
		} catch (OAuthException e) {
			log("OAuth Exception for: " + user);
			e.printStackTrace();
			return;
		}

		/*
		 * Get RotoGSS Team Info
		 */
		
		RotoGSSTeams gss_team = null;
		try { 
			Key team_key = new KeyFactory.Builder(RotoGSSUsers.class.getSimpleName(), user)
										 .addChild(RotoGSSTeams.class.getSimpleName(), team)
										 .getKey();
			gss_team = pm.getObjectById(RotoGSSTeams.class, team_key);
		} catch(JDOObjectNotFoundException e) {
			log("Team Not Found: " + team);
			e.printStackTrace();
			return;
		}
		
		Date lastUpdateToSpreadsheet = (gss_team.getLastUpdateTimestamp() == null) ? new Date(0) : gss_team.getLastUpdateTimestamp();

		Date queryDate = (req.getParameter("qDate") == null) ? lastUpdateToSpreadsheet : new Date(Long.parseLong(req.getParameter("qDate")));

		if(gss_team.getGoogleTableId() != null) {
			Key league_key = gss_team.getTeamKey().getParent();
			Key game_key = league_key.getParent();
    		
			String table_url = gss_team.getGoogleTableId();
			int table_id_slash = table_url.lastIndexOf("/");
			String table_id = table_url.substring(table_id_slash+1);
			String spreadsheet_id = gss_team.getGoogleLeagueSpreadsheet().substring(12); // "spreadsheet:"

			// Get a map for all the games stats and display names
			Map<Key, String> stat_map = new HashMap<Key, String>();
    		stat_map = getGameStatMap(game_key);
			Map<Key, String> team_names_map = new HashMap<Key, String>();
			team_names_map = getTeamNamesMap(league_key);

			Query query = pm.newQuery(YahooLeagueTeamStandings.class);
			query.setFilter("leagueKey == leagueKeyParam");
			query.setFilter("timestamp >= timestampParam");
			query.setOrdering("timestamp, leagueKey, teamRank");
			query.declareParameters("com.google.appengine.api.datastore.Key leagueKeyParam, java.util.Date timestampParam");
			if(lastCursorString != null) {
				log("Found Cursor: " + lastCursorString);
		        Cursor lastCursor = Cursor.fromWebSafeString(lastCursorString);
		        Map<String, Object> extensionMap = new HashMap<String, Object>();
		        extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, lastCursor);
		        query.setExtensions(extensionMap);
			} else {
				log("Didn't find cursor");
			}
			query.setRange(0, Long.valueOf(NUM_TEAMS));
			List<YahooLeagueTeamStandings> results;
			URL tableRecordsUrl = new URL(BASE_SS_URL + spreadsheet_id + "/records/" + table_id);
			RecordFeed recordFeed = new RecordFeed();
			if(USE_BATCH) {
				try {
					recordFeed = spreadClient.getFeed(tableRecordsUrl, RecordFeed.class);
				} catch (ServiceException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			Date lastTimestamp = gss_team.getLastUpdateTimestamp();
			try {
				results = (List<YahooLeagueTeamStandings>) query.execute(league_key, queryDate);
				int results_count = results.size();
				int i = 0;
				if(results.iterator().hasNext()) {
	    			for(YahooLeagueTeamStandings standing : results) {
	    				lastTimestamp = standing.getTimestamp();
	    				log("Standing Timestamp: " + lastTimestamp.toString());
						String entryHashString = gss_team.getGoogleLeagueSpreadsheet() + standing.getStandingsKey().toString();
						byte[] entryHashBytes = entryHashString.getBytes("UTF-8");
    					byte[] entryDigest = m.digest(entryHashBytes);
    					m.reset();
    					String entryDigestString = new String(entryDigest);
						if(!recordToGData(entryDigestString)) {
							log("Creating record with digest: " + entryDigestString);
		    				RecordEntry newRecord = createRecordFromStanding(standing, team_names_map, stat_map);
		    				if(USE_BATCH) {
			    				BatchUtils.setBatchId(newRecord, String.valueOf(i));
			    				BatchUtils.setBatchOperationType(newRecord, BatchOperationType.INSERT);
			    				i++;
			    				
			    				recordFeed.getEntries().add(newRecord);
		    				} else {
			    				try {
			    					RecordEntry insertedRecord = spreadClient.insert(tableRecordsUrl, newRecord);
									digestToDatastore(entryDigestString);
									log("Inserted Record: " + insertedRecord.getId() + " with digest " + entryDigestString);
								} catch (ServiceException e) {
									log("ServiceException Adding Record: " + e.toString());
									e.printStackTrace();
								}
		    				}
	    				} else {
		    				log("Found Entry");
	    				}
	    			}
				}
                if(results_count == NUM_TEAMS) callSelf = true;

			} finally {
				gss_team.setLastUpdateTimestamp(lastTimestamp);
				Transaction tx = pm.currentTransaction();
				try {
					tx.begin();
					pm.makePersistent(gss_team);
					tx.commit();
				} finally {
					if(tx.isActive()) tx.rollback();
				}
				query.closeAll();
			}
			if(USE_BATCH) {
				try {
					
					log("recordFeed: " + recordFeed.toString());
					
					Link batchLink = recordFeed.getFeedBatchLink();
					List<Link> links = recordFeed.getLinks();
					
					for(Link link : links) {
						log(link.getRel() + ":: " + link.getType() + ": " + link.getHref());
					}
					log("Inserting Batch Records");
					log("batchLink: " + batchLink.getHref());

					RecordFeed batchResultFeed = spreadClient.batch(new URL(batchLink.getHref()), recordFeed);
					checkBatchFeed(batchResultFeed);
				}  catch (ServiceException e) {
					log("ServiceException Adding Record: " + e.toString());
					e.printStackTrace();
				}
				
			}
	    	if(callSelf) {
				log("Calling self for the "+ n + " time.");
		    	n++;
	            Cursor currentCursor = JDOCursorHelper.getCursor(results);
	            String currentCursorString = currentCursor.toWebSafeString();
	            queue.add(url(req.getServletPath())
	            	 .param("user", user)
	            	 .param("team", team)
	            	 .param("qDate", String.valueOf(queryDate.getTime()))
	            	 .param("cursor", currentCursorString)
	            	 .param("nth", String.valueOf(n)));
	    	}
			
		} else {
			log("SpreadsheetTableId Not Found");
		}
	}

	private void setMessageDigester() {
		try {
			m = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			log("NoSuchAlgorithm");
			e.printStackTrace();
		}
	}
	
	private void setOAuthCredentials(RotoGSSUsers gss_user) throws OAuthException {

		GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
		oauthParameters.setOAuthConsumerKey(appConfig.getGoogleConsumerKey());
		oauthParameters.setOAuthConsumerSecret(appConfig.getGoogleConsumerSecret());
		oauthParameters.setOAuthToken(gss_user.getGoogleAccessToken());
		oauthParameters.setOAuthTokenSecret(gss_user.getGoogleAccessTokenSecret());

		try {
			docsClient.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());
			spreadClient.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());
		} catch (OAuthException e) {
			log("OAuthException: " + e.toString());
			e.printStackTrace();
			throw e;
		}
		
	}

	private Map<Key, String> getGameStatMap(Key game_key) {
		Map<Key, String> stat_map = new HashMap<Key, String>();
		YahooGames game = pm.getObjectById(YahooGames.class, game_key);
		Set<YahooGamesStats> game_stats = game.getYahooGamesStats();
		if(game_stats.iterator().hasNext()) {
			for(YahooGamesStats game_stat : game_stats) {
				stat_map.put(game_stat.getStatsKey(), game_stat.getStatDisplayName());
			}
		}

		return stat_map;
	}

	private Map<Key, String> getTeamNamesMap(Key league_key) {
		Map<Key, String> team_names_map = new HashMap<Key, String>();
		YahooLeagues league = pm.getObjectById(YahooLeagues.class, league_key);
		NUM_TEAMS = league.getNumTeams();
		Set<YahooLeagueTeams> teams = league.getYahooLeagueTeams();
		if(teams.iterator().hasNext()) {
			for(YahooLeagueTeams team : teams) {
				team_names_map.put(team.getTeamKey(), team.getName());
			}
		}
		return team_names_map;
	}

	private RecordEntry createRecordFromStanding(
			YahooLeagueTeamStandings standing, Map<Key, String> team_names_map,
			Map<Key, String> stat_map) {

		RecordEntry newRecord = new RecordEntry();
		Key standing_key = standing.getStandingsKey();
		String date = DateFormat.getDateInstance(DateFormat.SHORT).format(standing.getTimestamp());
		newRecord.addField(new Field(null, "Team Key", standing_key.getParent().getName()));
		newRecord.addField(new Field(null, "Team Name", team_names_map.get(standing_key.getParent())));
		newRecord.addField(new Field(null, "Date", date));
		newRecord.addField(new Field(null, "Rank", String.valueOf(standing.getTeamRank())));
		newRecord.addField(new Field(null, "Total", String.valueOf(standing.getTeamPoints())));
		newRecord.addField(new Field(null, "Change", String.valueOf(standing.getTeamPointsChange())));
		List<Key> stat_keys = standing.getStatKeys();
		List<Double> stat_values = standing.getStatValues();
		int stat_num = stat_keys.size();
		for(int i=0;i<stat_num;i++) {
			newRecord.addField(new Field(null, stat_map.get(stat_keys.get(i)), String.valueOf(stat_values.get(i))));
		}

		return newRecord;
	}
	
	private void checkBatchFeed(RecordFeed resultsFeed) {
		// Make sure all the operations were successful.
		for (RecordEntry entry : resultsFeed.getEntries()) {
		  
			String batchId = BatchUtils.getBatchId(entry);
			if (!BatchUtils.isSuccess(entry)) {
				BatchStatus status = BatchUtils.getBatchStatus(entry);
				log("Failed entry");
				log("\t" + batchId + " failed (" + status.getReason() + ") " + status.getContent());
		  }
		}

	}
	
	private void digestToDatastore(String digest) {
		// TODO Auto-generated method stub
		String readable;
		try {
			readable = URLEncoder.encode(digest, "UTF-8");
			Entity ent = new Entity("Z", readable);
			datastore.put(ent);
		} catch (UnsupportedEncodingException e) {
			log("Unsupported encoding");  // This shouldn't occur
			e.printStackTrace();
		}
	}
	
	private boolean recordToGData(String digest) {
		// TODO Auto-generated method stub
		try {
			String readable = URLEncoder.encode(digest, "UTF-8");
			Key key = KeyFactory.createKey("Z", readable);
			datastore.get(key);
		} catch(EntityNotFoundException e) {
			return false;
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			return false; // This won't happen.
		}
		return true;
	}

}
